﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Util;
using NAnt.SVN.Util;
using NAnt.SVN.Attributes;
using NAnt.SVN.Process;
using System.Globalization;

namespace NAnt.SVN.Tasks
{
    /// <summary>
    /// Generic SVN task based on the svn command.
    /// $Id$
    /// </summary>
    [TaskName("svn")]
    public class SVNTask : BaseExtensionTask
    {
        #region Private Fields

        private string command = string.Empty;
        private string formattedoptions = string.Empty;
        private List<string> options = new List<string>();
        private int exitcode = 0;
        private bool runhelponfail = true;
        private bool showhelp = false;
        AbstractProcessRunner runner;

        #endregion

        #region Protected Fields

        protected string rawoutput = string.Empty;

        #endregion

        #region Public Fields

        /// <summary>
        /// The command value
        /// </summary>
        [TaskAttribute("command",Required=false)]
        [CommandValidator()]
        public virtual string Command
        {
            get { return command; }
            set { command = value; }
        }

        /// <summary>
        /// Set the options
        /// </summary>
        public List<string> Options 
        {
            get { return options; }
            set { options = value; }
        }

        /// <summary>
        /// The process runner
        /// </summary>
        public virtual AbstractProcessRunner Runner
        {
            get { return runner; }
            set { runner = value; }
        }

        /// <summary>
        /// This is the actual options parameter. It corresponds 
        /// to the options property but joined with ' '. Every option
        /// is splitted with a space
        /// <remarks>The list Options is cleared when a value is set. 
        /// Use Options.Add instead.</remarks>
        /// TODO: parse this in order to cope with situations 
        /// as -FLAG 'value with space'
        /// </summary>
        [TaskAttribute("options", Required = false)]
        [StringValidator()]
        public string FormattedOptions
        {
            get { return string.Join(" ", Options.ToArray()); }
            set {
                string[] values = value.Split(' ');
                Options.Clear();
                Options.AddRange(values);
            }
        }

        /// <summary>
        /// Returns true if the options contain the xml output flag
        /// </summary>
        public bool IsXmlOutput
        {
            get {
                foreach (string o in Options)
                {
                    if (o.Contains(SVNFlags.XMLOUTPUT))
                        return true;
                }
                return false;
            }
        }

        /// <summary>
        /// Get the process output as a string
        /// </summary>
        public string Output
        {
            get;
            protected set;
        }

        /// <summary>
        /// Get the process output as a xml document, only if
        /// the xml output flag is defined in the options.
        /// </summary>
        public XmlDocument XmlOutput
        {
            get;
            protected set;
        }

        /// <summary>
        /// Run help command on fail, this is in fact an internal flag
        /// </summary>
        public bool RunHelpOnFail
        {
            get { return runhelponfail; }
            set { runhelponfail = value; }
        }

        /// <summary>
        /// Show svn help if something goes wring
        /// </summary>
        [TaskAttribute("help", Required = false)]
        [BooleanValidator()]
        public bool ShowHelp
        {
            get { return showhelp; }
            set { showhelp = value; }
        }

        #endregion

        #region Ctor functions

        /// <summary>
        /// ctor
        /// </summary>
        public SVNTask() 
        {
        }

        /// <summary>
        /// Override the initialize function for startup
        /// </summary>
        protected virtual void InitializeSVNTask()
        { 
        }

        #endregion

        /// <summary>
        /// Run the task
        /// </summary>
        protected override void ExecuteTask()
        {
            runProcess();        
        }

        #region Process functions

        /// <summary>
        /// Callback
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnHasRun(ProcessRunnerData e)
        {
            //Get the values
            rawoutput = e.RawOutput;
            Output = e.Output;
            exitcode = e.ExitCode;
            //Process the results
            if (e.ExitCode == 0)
            {
                Log(Level.Info, getString("SVN0102"));
                //Convert to xml
                if (IsXmlOutput)
                {
                    try
                    {
                        XmlOutput = new XmlDocument();
                        XmlOutput.LoadXml(rawoutput);
                    }
                    catch (Exception e1)
                    {
                        Log(Level.Error, getString("SVN0304"), e1.Message);

                    }
                }
            }
            else
            {
                Log(Level.Warning, getString("SVN0202"));
#if DEBUG
                
                //Show the console output
                Log(Level.Warning, Output);
                //Show the raw command
                Log(Level.Warning, string.Format(getString("SVN0203"), Command, FormattedOptions));

#endif
                //Run the help command
                runHelp();
            }
            //Set the parameter
            PropertiesManager.SetProperty(Resources.PropertyNames.LastOutput, Output);
            //Log verbose
            Log(Level.Verbose, Output);
        }

        /// <summary>
        /// Run the process
        /// </summary>
        private void runProcess()
        {
            //Initalize the task
            InitializeSVNTask();
            //Log it
            Log(Level.Info, string.Format(getString("SVN0101"), Command));
            Log(Level.Verbose, string.Format(getString("SVN0201"), FormattedOptions));
            //Configure processrunner
            if(Runner == null) Runner = new ProcessRunner();
            Runner.OnHasRun += new AbstractProcessRunner.OnHasRunEventHandler(OnHasRun);
            //Execute
            Runner.Execute(this);
        }

        #endregion

        #region Log function

        /// <summary>
        /// Log a message
        /// </summary>
        /// <param name="messageLevel"></param>
        /// <param name="message"></param>
        public override void Log(Level messageLevel, string message)
        {
            LoggingInstance.LogMessage(messageLevel, message);
        }

        /// <summary>
        /// Log a message
        /// </summary>
        /// <param name="messageLevel"></param>
        /// <param name="message"></param>
        /// <param name="args"></param>
        public override void Log(Level messageLevel, string message, params object[] args)
        {
            string logMessage = string.Format(CultureInfo.InvariantCulture, message, args);
            Log(messageLevel, logMessage);
        }

        #endregion

        #region Misc functions

        /// <summary>
        /// Run help on fail
        /// </summary>
        void runHelp()
        {
            if (RunHelpOnFail && ShowHelp)
            {
                SVNTask help = new SVNTask();
                this.CopyTo(help);
                help.RunHelpOnFail = false;
                help.Command = SVNCommands.HELP;
                help.FormattedOptions = this.Command;
                help.ExecuteTask();
                Console.Out.WriteLine(Environment.NewLine + help.Output + Environment.NewLine);
            }
        }

        /// <summary>
        /// Internal function to get a string
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected string getString(string key)
        {
            return Strings.GetString(key);
        }

        /// <summary>
        /// Encapsulate a string with a quote
        /// </summary>
        /// <param name="value"></param>
        /// <param name="doublequote"></param>
        /// <returns></returns>
        protected string quote(string value, bool doublequote)
        {
            return string.Format("{1}{0}{1}", value, (doublequote) ? "\"" : "'");
        }

        /// <summary>
        /// Encapsulate a string with a quote, by default double quotes (").
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected string quote(string value)
        {
            return quote(value, true);
        }

        #endregion
    }
}
